//
// Copyright (c) 2009 All Right Reserved
//
// Stephen Toub, Leslie Sanford
// stoub@microsoft.com, jabberdabber@hotmail.com
// 2009-01-01
// Contains ...
// Classes for interop with Win32 MCI and low-level MIDI API
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
using JetBrains.Annotations;
namespace LargoCommon.Midi
{
/// Provides access to the Media Control Interface and other MIDI functionality.
//// internal
public static class MidiInternalDevices {
//// Given in OutputDeviceBase
//// private const int MOM_OPEN = 0x3C7; private const int MOM_CLOSE = 0x3C8; private const int MOM_DONE = 0x3C9;
#region Fields
//// internal const int CallbackFunction = 0x00030000;
/// The default output MIDI device id. Midi Handle.
private const int MidiMapper = -1;
#endregion
///
/// Synchronization Lock.
///
private static readonly object ThisLock = new object();
//// Used for synchronization of all MIDI-related operations.
//// private static readonly object midiLock = new object();
#region Fields
/// The number of references to the currently open MIDI device.
private static int numberReferences; //// = 0;
#endregion
#region Properties
///
/// Gets Device Count (unused).
///
[UsedImplicitly]
public static int CountDevices => NativeMethods.MidiOutGetNumDevs();
///
/// Gets Handle of the midi device.
///
public static MidiDeviceHandle MidiDeviceHandle { get; private set; }
///
/// Gets a value indicating whether player was open.
///
public static bool IsOpen => MidiDeviceHandle != null && MidiDeviceHandle.IsOpen;
#endregion
#region Midi Output device - open, reset (Win32 Midi Output Functions)
///
/// Open the default MIDI device.
///
///
/// This is necessary only when playing individual events.
///
public static void OpenMidi()
{
lock (ThisLock) {
// Open the MIDI device if it hasn't already been opened.
if (numberReferences == 0) {
InternalOpenMidi();
}
numberReferences++;
}
}
/// Opens the MIDI device without regard for whether it has already been opened.
public static void InternalOpenMidi() {
// Open the default MIDI device - Open the MIDI_MAPPER device (default).
OpenMidiOut(MidiMapper);
}
/* Unfinished management of Midi devices
///
/// Reset Midi Out (unused).
///
[UsedImplicitly]
public static void ResetMidiOut() {
lock (thisLock) {
// Reset the OutputDevice.
int result = NativeMethods.midiOutReset(MidiDeviceHandle.Handle);
if (result == MidiDeviceException.SystemNoError) {
//// while (bufferCount > 0) { Monitor.Wait(thisLock); }
}
else {
// Throw an exception.
throw new OutputDeviceException(result);
}
}
} */
#endregion
#region Midi Output device - close (Win32 Midi Output Functions)
/* Unfinished management of Midi devices
/// Close the default MIDI device.
[UsedImplicitly]
public static void CloseMidi() {
lock (thisLock) {
// Close the MIDI device if no one else is using it
if (numberReferences == 0) {
return;
}
numberReferences--;
if (numberReferences == 0) {
InternalCloseMidi();
}
}
}*/
///
/// Prepares the midi.
///
public static void PrepareMidi() {
//// We can't play using MCI if we already have an open handle to the default
//// MIDI device. As such, we'll temporarily close it if its open and then
//// when we're done reopen it if it was open.
if (IsOpen) {
InternalCloseMidi();
}
}
/// Closes the MIDI device without regard for the reference count.
public static void InternalCloseMidi() {
// Close the MIDI device if it is open
if (MidiDeviceHandle == null) {
return;
}
MidiDeviceHandle.Dispose();
MidiDeviceHandle = null;
numberReferences = 0;
}
/// Close the specified MIDI output device.
/// Handle of the MIDI output device.
public static void CloseMidiOut(int handle) {
var result = NativeMethods.MidiOutClose(handle); //// MidiError ... this.midiHandle
if (result != 0) { //// MidiSystemErrorNOERROR
ThrowMciError(result, "Could not close MIDI out.");
//// throw new Exception("Closing MIDI device failed with error " + result.ToString(CultureInfo.CurrentCulture));
}
//// if (MidiDeviceHandle != null) { MidiDeviceHandle.Close(); }
}
#endregion
#region Midi Output devices - number, outcaps
///
/// Midi GetDeviceCaps.
///
/// Device Id.
/// Device Midi out Caps.
/// Given size.
/// Returns value.
public static MidiError GetDeviceCaps(int deviceId, ref MidiOutcaps deviceLpCaps, int size) {
return (MidiError)NativeMethods.MidiOutGetDevCaps(deviceId, ref deviceLpCaps, size);
}
///
/// Get Device Capabilities.
///
/// Device Id.
/// Returns value.
[UsedImplicitly]
public static MidiOutcaps GetDeviceCapabilities(int deviceId) {
var caps = new MidiOutcaps(0, 0, 0, string.Empty, 0, 0, 0, 0, 0);
// Get the device's capabilities.
var result = NativeMethods.MidiOutGetDevCaps(deviceId, ref caps, Marshal.SizeOf(caps));
// If the capabilities could not be retrieved.
if (result != (int)MidiError.SystemNoError) {
// Throw an exception.
throw new OutputDeviceException(result);
}
return caps;
}
#endregion
#region MCI error
/// Throws an exception based on the MCI error number.
/// The MCI error number.
/// The message to throw if an MCI message can't be retrieved.
public static void ThrowMciError(int rv, string optionalMessage) { //// [VL] was private
//// If there is an error, throw an exception with the best error description we can get.
//// var error = GetMciError(rv) ?? "Could not close MIDI out.";
//// error += optionalMessage; //// [VL] optionalMessage was unused
//// Could not open MIDIOut (Device is already used...)
#warning What here?
//// throw new InvalidOperationException(error);
}
#endregion
///
/// Midi GetNumberOfDevices.
///
/// Returns value.
public static short GetNumberOfDevices() { //// Int16
return NativeMethods.MidiOutGetNumDevs();
}
#region Private Support
/// Opens the specified MIDI output device.
/// The ID of the MIDI output device to be opened.
private static void OpenMidiOut(int deviceId) {
var handle = 0;
//// RunningStatusEnabled = false;
var result = NativeMethods.MidiOutOpen(ref handle, deviceId, IntPtr.Zero, 0, 0); //// MidiError ... ref this.midiHandle, -1
//// midiOutProc = HandleMessage;
//// int result = NativeMethods.MidiOutOpen(ref handle, deviceId, midiOutProc, 0, CallbackFunction);
if (result != 0) { //// MidiDeviceException.SystemNoError
ThrowMciError(result, "Could not open MIDI out.");
//// throw new OutputDeviceException(result);
//// throw new Exception("Opening MIDI device failed with error " + result.ToString(CultureInfo.CurrentCulture));
}
MidiDeviceHandle = new MidiDeviceHandle(handle);
}
#endregion
/* Unused
#region Private Errors
/// Gets the description for the given MCI error code.
/// The error code for which we need an error description.
/// The error description (or null if none exists).
private static string GetMciError(int errorCode) {
var buffer = new StringBuilder(255); // max string should be 128, so 255 just to be safe
return NativeMethods.MciGetErrorString(errorCode, buffer, buffer.Capacity) == 0 ? null : buffer.ToString();
}
#endregion
*/
//// Handles Windows messages.
//// private static void HandleMessage(int handle, int msg, int instance, int param1, int param2) { }
///
/// Native Methods.
///
private static class NativeMethods {
////
//// Prevents a default instance of the NativeMethods class from being created.
////
//// private NativeMethods() {}
#region Native Methods - Low-Level MIDI API - MidiOut Open, Reset (Win32 Midi Output Functions)
/// The midiOutOpen function opens a MIDI output device for playback.
/// Pointer to an HMIDIOUT handle.
/// Identifier of the MIDI output device that is to be opened.
/// Pointer to a callback function, an event handle, a thread identifier, or a handle of a window or thread called during MIDI playback to process messages related to the progress of the playback.
/// User instance data passed to the callback.
/// Callback flag for opening the device.
/// Returns SystemNoError if successful or an error otherwise.
[DllImport("winmm.dll", EntryPoint = "midiOutOpen", CharSet = CharSet.Ansi)] //// CharSet.Ansi
[SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")]
public static extern int MidiOutOpen(ref int lphMidiOut, int uDeviceId, IntPtr dwCallback, int dwInstance, int dwCallbackFlag); //// dwFlags
//// [DllImport("winmm.dll")]
//// private static extern int midiOutOpen(ref int handle, int deviceID, MidiOutProc proc, int instance, int flags);
///
/// Midis the out reset.
///
/// The handle.
/// Returns value.
[DllImport("winmm.dll", EntryPoint = "midiOutReset"), UsedImplicitly]
public static extern int MidiOutReset(int handle);
#endregion
#region Native Methods - Low-Level MIDI API - MidiOut Close (Win32 Midi Output Functions)
/// The midiOutClose function closes the specified MIDI output device.
/// Handle to the MIDI output device.
/// Returns SystemNoError if successful or an error otherwise.
[DllImport("winmm.dll", EntryPoint = "midiOutClose", CharSet = CharSet.Ansi)] //// CharSet.Ansi
[SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")]
public static extern int MidiOutClose(int hMidiOut);
//// [DllImport("winmm.dll")]
//// private static extern int midiOutClose(int handle);
#endregion
#region NativeMethods - Devices
///
/// MidiOut Get Number of Devices.
///
/// Returns value.
[DllImport("winmm.dll", EntryPoint = "midiOutGetNumDevs", CharSet = CharSet.Ansi)]
[SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")]
public static extern short MidiOutGetNumDevs(); //// Int16
///
/// MidiOut Get Device Caps.
///
/// Device Id.
/// Midi out Caps.
/// Given size.
///
/// Returns value.
///
[DllImport("winmm.dll", EntryPoint = "midiOutGetDevCapsA")]
[SuppressMessage("Microsoft.Portability", "CA1901:PInvokeDeclarationsShouldBePortable", MessageId = "return", Justification = "No other result found.")]
public static extern int MidiOutGetDevCaps(int uDeviceId, ref MidiOutcaps lpCaps, int uSize);
//// [DllImport("winmm.dll")]
//// protected static extern int midiOutGetDevCaps(int deviceID,
//// ref MidiOutcaps caps, int sizeOfMidiOutCaps);
#endregion
/// The mciGetErrorString function retrieves a string that describes the specified MCI error code.
/// Error code returned by the mciSendCommand or mciSendString function.
/// Pointer to a buffer that receives a null-terminated string describing the specified error.
/// Length of the buffer, in characters, pointed to by the previous parameter.
/// Returns non-zero if successful or 0 if the error code is not known.
/// Each string that MCI returns, whether data or an error description, can be a maximum of 128 characters.
//// Use string (or System.String) for a const char*, but StringBuilder for a char*.
[DllImport("winmm.dll", EntryPoint = "mciGetErrorStringA", CharSet = CharSet.Ansi)]
[UsedImplicitly]
//// CharSet.Ansi //// 11/2010 - MarshalAs
public static extern int MciGetErrorString(int fdwError, StringBuilder lpszErrorText, int cchErrorText);
//// public static extern int MciGetErrorString(int fdwError, [MarshalAs(UnmanagedType.LPWStr)]StringBuilder lpszErrorText, int cchErrorText);
//// BestFitMapping: Turns on or off some optimization behavior when converting from Unicode to ANSI
////[DllImport("winmm.dll", CharSet = CharSet.Ansi, BestFitMapping = true, ThrowOnUnmappableChar = true)]
////[return: MarshalAs(UnmanagedType.Bool)]
////public static extern bool mciGetErrorString(uint mciError, [MarshalAs(UnmanagedType.LPStr)] System.Text.StringBuilder pszText, uint cchText);
}
}
}